package com.dyava.commons.lock.redis;

import com.dyava.commons.lock.KeyLock;
import com.dyava.commons.lock.KeyLockTimeoutException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.core.StringRedisTemplate;

import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;

public class RedisKeyLock  implements KeyLock {
    static Logger log = LoggerFactory.getLogger(RedisKeyLock.class);
    public static final String REDIS_PRE = "KEYLOCK:";


    String key;
    RedisKeyLockPool redisKeyLockPool;
    /** 是否持有最外层的锁 */
    volatile boolean hold;
    /** 看门狗心跳 */
    volatile ScheduledFuture watchdogFuture;

    public String getKey() {
        return key;
    }

    public void setKey(String key) {
        this.key = key;
    }

    public RedisKeyLock(RedisKeyLockPool redisKeyLockPool, String key) {
        this.redisKeyLockPool = redisKeyLockPool;
        this.key = key;
    }

    private int getSleepNum(int tryNum){
        if(tryNum==1){
            return 50;
        }else if(tryNum==2){
            return 200;
        }else if(tryNum==3){
            return 800;
        }else {
            return 1600;
        }
    }

    StringRedisTemplate stringRedisTemplate(){
        return redisKeyLockPool.getStringRedisTemplate();
    }

    private String realRedisKey(String key){
        return REDIS_PRE+key;
    }
    void startWatchdog(){
        if(hold){
            log.trace("{} -> watchdog 启动",key);
            watchdogFuture = redisKeyLockPool.getThreadPoolWatchDog().schedule(() ->{
                log.trace("{} -> watchdog 运行",key);
                stringRedisTemplate().expire(realRedisKey(key), 60,TimeUnit.SECONDS);
                startWatchdog();
            }, 50, TimeUnit.SECONDS);
        }
    }

    void stopWatchdog(){
        if(watchdogFuture!=null){
            log.trace("{} -> watchdog 关闭",key);
            watchdogFuture.cancel(false);
            watchdogFuture = null;
        }
    }


    public boolean tryLock(){
        if(!redisKeyLockPool.isExistLockedKey(this)){
                log.trace("{} -> 尝试锁定...",key);
                boolean res = stringRedisTemplate().opsForValue().setIfAbsent(realRedisKey(key), "1", 60000,TimeUnit.MILLISECONDS);
                if(res){    //成功获得锁
                    log.trace("{} -> 获得锁",key);
                    hold = true; //自己标记为持有
                    redisKeyLockPool.registerHolder(this);
                    startWatchdog();
                    return true;
                }else{
                    log.trace("{} -> 抢锁失败",key);
                    return false;
                }
        }else{
            log.trace("{} -> 重入锁",key);
            return true;
        }
    }

    public void lock(int waitMsec){
        boolean locked=false;
        int tryNum=1;
        long endTime = System.currentTimeMillis() + waitMsec;
        while(System.currentTimeMillis() < endTime){
            if(tryLock()){
                locked=true;
                break;
            }else{
                try {
                    int sleepNum = getSleepNum(tryNum);
                    Thread.sleep(sleepNum);
                } catch (InterruptedException e) {
                    continue;
                }
            }
            tryNum++;
        }
        if(!locked){
            throw new KeyLockTimeoutException("lock 超时");
        }
    }


    @Override
    public void close(){
        if(hold){
            log.trace("{} -> 释放锁",key);
            stringRedisTemplate().delete(realRedisKey(key));
            hold = false;//自己标记为不持有
            redisKeyLockPool.unregisterHolder(this);
            stopWatchdog();
        }else{
            log.trace("{} -> 无锁状态下释放",key);
        }
    }
}
